package com.bdqn.controller;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.bdqn.pojo.User;
import com.bdqn.service.RedisService;
import com.bdqn.service.UserService;
import com.bdqn.shiro.MyShiroRealm;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpSession;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.mgt.RealmSecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 登录注销相关控制器
 *
 * @author LILIBO
 * @since 2024/9/21
 */
@Controller
public class IndexController {

    @Resource
    private UserService userService;

    @Resource
    private RedisService redisService;

    /**
     * 引入StringRedisTemplate组件bean
     */
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    // Redis中数据的key的前缀
    private final int EXCEPTION_COUNT = 5;
    private final String SHIRO_LOGIN_COUNT = "shiro_login_count_"; // 登录计数
    private final String SHIRO_IS_LOCK = "shiro_is_lock_"; // 锁定用户登录

    /**
     * 去登录页
     */
    @GetMapping("/login")
    public String toLogin() {
        return "login";
    }

    /**
     * 执行登录操作
     */
    @PostMapping("/login")
    public String doLogin(Model model, HttpSession session, String usrName, String usrPassword) {
        /* User loginUser = userService.login(usrName, usrPassword);
        if (loginUser != null) {
            session.setAttribute("loginUser", loginUser);
            return "redirect:/main"; // 重定向到首页
        } else {
            model.addAttribute("msg", "用户名或密码错误，登录失败！ ");
            return "login"; // 必须转发
        } */

        // 登录失败次数超过5次锁定1小时
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        // 构造一个Shiro令牌
        UsernamePasswordToken token = new UsernamePasswordToken(usrName, usrPassword);
        // 创建一个主体
        Subject subject = SecurityUtils.getSubject();
        // 使用Shiro安全框架进行登录验证
        try {
            // 用户锁定状态时，不执行登录验证
            if ("LOCK".equals(opsForValue.get(SHIRO_IS_LOCK + usrName))) {
                stringRedisTemplate.delete(SHIRO_LOGIN_COUNT + usrName); // 清空登录计
                model.addAttribute("msg", "该账号已锁定，1小时后再试！");
                return "login";
            }
            // 使用Shiro方式进行登录验证
            subject.login(token);
        } catch (UnknownAccountException | IncorrectCredentialsException e) {
            // 登录失败次数超过5次锁定1小时
            if (e instanceof IncorrectCredentialsException) {
                opsForValue.increment(SHIRO_LOGIN_COUNT + usrName, 1); // 登录失败次数+1
            } else {
                model.addAttribute("msg", "用户名或密码错误，登录失败！");
                return "login";
            }
            // 登录失败次数大于5次
            if (Integer.parseInt(Objects.requireNonNull(opsForValue.get(SHIRO_LOGIN_COUNT + usrName))) >= EXCEPTION_COUNT) {
                // 锁定该用户1小时
                opsForValue.set(SHIRO_IS_LOCK + usrName, "LOCK", 1, TimeUnit.HOURS);
                stringRedisTemplate.delete(SHIRO_LOGIN_COUNT + usrName); // 清空登录计数
                model.addAttribute("msg", "该账号已锁定，1小时后再试！");
                return "login";
            } else {
                model.addAttribute("msg", "用户名或密码错误，登录失败！（" + (EXCEPTION_COUNT - Integer.parseInt(Objects.requireNonNull(opsForValue.get(SHIRO_LOGIN_COUNT + usrName)))) + "次后账号将锁定）");
                return "login";
            }
        } catch (LockedAccountException e) {
            model.addAttribute("msg", "用户被禁用，登录失败！");
            return "login";
        } catch (AuthenticationException e) {
            model.addAttribute("msg", "认证异常，登录失败！");
            return "login";
        }
        // 如果登录成功清空该用户的计数和锁
        stringRedisTemplate.delete(SHIRO_LOGIN_COUNT + usrName); // 清空登录计
        stringRedisTemplate.delete(SHIRO_IS_LOCK + usrName); // 清空锁

        // 认证成功，获取登录的主体信息
        User loginUser = (User) subject.getPrincipal();

        // 清空当前用户权限缓存
        RealmSecurityManager rsm = (RealmSecurityManager) SecurityUtils.getSecurityManager();
        MyShiroRealm realm = (MyShiroRealm) rsm.getRealms().iterator().next();
        realm.clearMyCachedAuthorizationInfo();

        // 将认证的用户存储到Session
        session.setAttribute("loginUser", loginUser);
        System.out.println(session.toString() + "\nWeb Session: id=" + session.getId() + " usrName=" + ((User) session.getAttribute("loginUser")).getUsrName());
        Session shiroSession = subject.getSession();
        System.out.println(shiroSession.toString() + "\nShiro Session: id=" + shiroSession.getId() + " usrName=" + ((User) shiroSession.getAttribute("loginUser")).getUsrName());

        return "redirect:/main"; // 重定向到首页
    }

    /**
     * 跳转到主页
     */
    @GetMapping("/main")
    public String toMain() {
        return "main";
    }

    /**
     * 退出登录
     */
    @GetMapping("/logout")
    public String logout(HttpSession session) {
        session.removeAttribute("loginUser");
        // session.invalidate(); // 销毁Session
        // Shiro注销，会销毁当前Session
        SecurityUtils.getSubject().logout();
        return "redirect:/login";
    }

    /**
     * 权限不足时跳转页面
     */
    @RequestMapping("/403")
    public String unauthorized() {
        return "403";
    }

    /**
     * [前后端分离]用户登录获取Token
     *
     * @param body
     * @return
     */
    @ResponseBody // Ajax
    @PostMapping("/api/token/login")
    public String tokenGenerate(@RequestBody String body) {
        JSONObject parse = (JSONObject) JSON.parse(body);
        String usrName = (String) parse.get("usrName");
        String usrPassword = (String) parse.get("usrPassword");
        Map<String, Object> resMap = new HashMap<>();
        User loginUser = userService.login(usrName, usrPassword);
        if (loginUser == null) {
            resMap.put("code", 401); // 登录失败
            resMap.put("msg", "登录失败");
        } else {
            boolean isok = redisService.set("testLoginUser", loginUser);
            System.out.println(isok);
            // 用户登录成功，存储Redis并返回Token
            String token = redisService.generateToken(loginUser);
            resMap.put("code", 0); // 登录成功
            resMap.put("msg", "登录成功");
            Map<String, Object> tokenMap = new HashMap<>();
            tokenMap.put("token", token);
            resMap.put("data", tokenMap);
        }
        // 响应数据给前端
        return JSON.toJSONString(resMap);
    }

    /**
     * [前后端分离]用户登录验证Token，修改密码
     *
     * @param body
     * @return
     */
    @ResponseBody // Ajax
    @PostMapping("/api/token/apply")
    public String tokenApply(@RequestBody String body) {
        JSONObject parse = (JSONObject) JSON.parse(body);
        String token = (String) parse.get("token");
        String newPassword = (String) parse.get("newPassword");
        Map<String, Object> resMap = new HashMap<>();
        User loginUser = redisService.checkToken(token);
        if (loginUser != null) {
            // 测试，设置新密码
            loginUser.setUsrPassword(newPassword);
            boolean isok = userService.updateById(loginUser);
            if (isok) {
                resMap.put("code", 0); // 操作成功
                resMap.put("msg", "操作成功");
            } else {
                resMap.put("code", 101); // 操作失败
                resMap.put("msg", "操作失败");
            }
        } else {
            resMap.put("code", 401); // 验证失败
            resMap.put("msg", "验证失败");
        }
        // 响应数据给前端
        return JSON.toJSONString(resMap);
    }

}
